package extensions;

import java.io.IOException;
import java.lang.Thread.State;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import extensions.games.AbstractGame;
import extensions.packet.ClientPacket;
import extensions.romario.packet.AbstractPacket;
import extensions.romario.packet.AbstractPacket;
import extensions.romario.server.MainJME;

public class Server
{

    private static final int ACCEPTING_SOCKET_TIMEOUT = 4000;
    private static final int ACCEPT_CLIENT_THREAD_SLEEP = 100;// ms
    private ServerSocket socket;
    private Map<Integer, NetworkClient> _clients = new HashMap<Integer, NetworkClient>();
    private AbstractGame _game;
    private boolean _runAcceptingClientThread = true;
    private NetworkClientListener _clientListener;
    private static int _idGenerator = 1;
    private final MainJME _application;

    @SuppressWarnings("unchecked")
    public <T extends AbstractGame> T getGame()
    {
	return (T) _game;
    }

    public Server(int port, MainJME application) throws IOException
    {
	_application = application;
	socket = new ServerSocket(port);

	startAcceptingClientsThread();
	startBroadcastThread();
    }
    private Thread _acceptingClientsThread;
    private BroadcastThread _broadcastThread;

    protected final void startAcceptingClientsThread()
    {
	if (_acceptingClientsThread != null
		&& _acceptingClientsThread.getState() != State.TERMINATED)
	{
	    throw new IllegalStateException(
		    "The client accepting thread is already created.");
	}

	System.out.println("Starting AcceptingClientsThread");
	_acceptingClientsThread = new Thread(new Runnable()
	{

	    @Override
	    public void run()
	    {
		System.out.println("Accepting clients...");
		_runAcceptingClientThread = true;

		try
		{
		    while (_runAcceptingClientThread)
		    {

			socket.setSoTimeout(ACCEPTING_SOCKET_TIMEOUT);
			try
			{
			    Socket clientSocket = socket.accept();
			    NetworkClient client = new NetworkClient(
				    _idGenerator++, clientSocket, Server.this,
				    _application);
			    _clients.put(client.getId(), client);
			    System.out.println("Cliente nuevo:"
				    + client.getSocket());
			    if (_clientListener != null)
			    {
				_clientListener.onClientJoin(client);
			    }

			} catch (SocketTimeoutException e)
			{// Timeout expired
			}

			Thread.sleep(ACCEPT_CLIENT_THREAD_SLEEP);
		    }
		} catch (IOException ex)
		{
		    Logger.getLogger(Server.class.getName()).log(Level.SEVERE,
			    null, ex);
		} catch (InterruptedException ex)
		{
		    Logger.getLogger(Server.class.getName()).log(Level.SEVERE,
			    null, ex);
		}
		System.out.println("Stop accepting clients.");
	    }
	});

	_acceptingClientsThread.setName("AcceptingClientsThread");
	_acceptingClientsThread.start();
    }

    protected final void startBroadcastThread()
    {
	if (_broadcastThread != null
		&& _broadcastThread.getState() != State.TERMINATED)
	{
	    throw new IllegalStateException(
		    "The broadcast thread is already created.");
	}

	try
	{
	    _broadcastThread = new BroadcastThread();
	    _broadcastThread.start();
	} catch (IOException e)
	{
	    e.printStackTrace();
	}
    }

    public void sendPacketToAll(AbstractPacket packet) throws IOException
    {
	for (NetworkClient client : _clients.values())
	{
	    client.sendPacket(packet);
	}
    }

    public void sendPacketTo(AbstractPacket packet, NetworkClient client)
	    throws IOException
    {
	client.sendPacket(packet);
    }

    /**
     * Called when a client receives a packet.
     * 
     * @param packet
     * @param client
     */
    public void onPacketReceived(ClientPacket packet, NetworkClient client)
    {
	System.out.println("Paquete de '"
		+ client.getSocket().getInetAddress().getHostAddress() + "': "
		+ packet);
    }

    public void close()
    {
	_runAcceptingClientThread = false;
	_broadcastThread.stopRunning();

	for (NetworkClient client : _clients.values())
	{
	    client.close();
	}
    }

    public int getClientsNumber()
    {
	return _clients.size();
    }

    public void setNewClientListener(NetworkClientListener listener)
    {
	_clientListener = listener;
    }

    public void onClientDisconnected(NetworkClient client)
    {
	if (_clientListener != null)
	{
	    _clientListener.onClientDisconnected(client);
	}
    }
}